/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-12-12     xu       the first version
 */
#include <stdlib.h>
#include <fal.h>
#include <rtthread.h>
#include "W25Qx.h"

#define DBG_TAG           "fal.w25q"
#define DBG_LVL           DBG_LOG
#include <rtdbg.h>

static rt_bool_t debug_on = RT_TRUE;

#ifdef LOG_D
#undef LOG_D
#define LOG_D(fmt, ...) \
    do { \
        if (debug_on) { \
            dbg_log_line("D", 0, fmt, ##__VA_ARGS__); \
        } \
    } while (0)
#endif

static int init(void)
{
    uint8_t status = W25Qx_OK;

    status = BSP_W25Qx_Init();
    if (status != W25Qx_OK) {
        LOG_E("W25Q128 Init FAIL!");
        return -1;
    }

    LOG_I("W25Q128 Init OK!");
    debug_on = RT_FALSE;
    return 0;
}

static int read(long offset, uint8_t *buf, size_t size)
{
    uint8_t status = W25Qx_OK;

    RT_ASSERT(buf && size);
    status = BSP_W25Qx_Read(buf, offset, size);
    if (status != W25Qx_OK) {
        LOG_E("W25Q128 Read 0x%X, %d FAIL!", offset, size);
        return -1;
    }

    LOG_D("W25Q128 Read 0x%X, %d OK!", offset, size);
    return size;
}

static int write(long offset, const uint8_t *buf, size_t size)
{
    RT_ASSERT(buf && size);
    LOG_D("W25Q128 Write 0x%X, %d start...", offset, size);

    size_t batch_size = W25Q128FV_PAGE_SIZE;
    for (size_t i = 0; i < size; i += batch_size) {
        uint32_t count = (size - i) < batch_size ? (size - i) : batch_size;
        long pos = offset + i;
        uint8_t status = BSP_W25Qx_Write((uint8_t*) &buf[i], pos, count);
        if (status != W25Qx_OK) {
            LOG_E("W25Q128 Write 0x%X FAIL!", pos);
            return -1;
        }
    }

    LOG_D("W25Q128 Write 0x%X, %d done!", offset, size);
    return size;
}

static int erase(long offset, size_t size)
{
    RT_ASSERT(size);
    LOG_D("W25Q128 Erase 0x%X %d start...", offset, size);

    if (offset & (W25Q128FV_SECTOR_SIZE - 1)) {
        LOG_W("Warning: offset %d is not sector aligned!", offset);
        offset -= (offset & (W25Q128FV_SECTOR_SIZE - 1));
        LOG_W("Warning: offset aligned to %d!", offset);
    }

    if (size & (W25Q128FV_SECTOR_SIZE - 1)) {
        LOG_W("Warning: %d is not times of sector size(%d)!", size, W25Q128FV_SECTOR_SIZE);
        size -= (size & (W25Q128FV_SECTOR_SIZE - 1));
        size += W25Q128FV_SECTOR_SIZE;
        LOG_W("Warning: actual erase size is %d!", size);
    }

    size_t batch_size = W25Q128FV_SECTOR_SIZE;
    for (size_t i = 0; i < size; i += batch_size) {
        long pos = offset + i;
        RT_ASSERT((pos & (W25Q128FV_SECTOR_SIZE - 1)) == 0);
        uint8_t status = BSP_W25Qx_Erase_Block(pos);
        if (status != W25Qx_OK) {
            LOG_E("W25Q128 Erase 0x%X FAIL!", pos);
            return -1;
        }
    }

    LOG_D("W25Q128 Erase 0x%X %d OK!", offset, size);
    return size;
}

static int fal_dbg(int argc, char* argv[])
{
    debug_on = argc > 1 ? atoi(argv[1]) : RT_FALSE;
    LOG_I("W25Q128 debug_on = %d", debug_on);
    return 0;
}
MSH_CMD_EXPORT(fal_dbg, "FAL debug on/off");

struct fal_flash_dev w25q128_spi_flash =
{
    .name       = NOR_FLASH_DEV_NAME,
    .addr       = 0,
    .len        = 16 * 1024 * 1024,
    .blk_size   = 4 * 1024,
    .ops        = {init, read, write, erase},
    .write_gran = 1
};
